
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** @author  John Miller, Michael E. Cotterell
 *  @version 1.0
 *  @see     LICENSE (MIT style license file).
 */

package scalation.stat

import math.sqrt

import scalation.linalgebra.VectorD
import scalation.random.{Quantile, Uniform}
import scalation.util.Error

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** This class is used to collect values and compute sample statistics on them
 *  (e.g., Waiting Time) using the method of Batch Means.
 *  @param name      the name for this statistic (e.g., WatingTime or tellerQ)
 *  @param batchSize 
 *  @param unbiased  whether the estimators are restricted to be unbiased.
 */
class BatchStat(name: String, private var batchSize: Int = 10, unbiased: Boolean = false) 
      extends Statistic(name, unbiased) with Error
{

    /** The number of usable batches
     */
    def b = (n % batchSize) - 1

    /** Sum of the sample values
     */
    protected var sums = new VectorD(n)

    /** Batch means
     */
    protected var means = new VectorD(n)

    /** Sum of the sample values squared
     */
    protected var sumSqs = new VectorD(n)

    /** the minimum sample value
     */
    protected var minXs = new VectorD(n)

    /** the maximum sample value
     */
    protected var maxXs = new VectorD(n)

    /** The denominator is one less for unbiased vs. maximum likelihood estimators
     */
    private def den = (if (unbiased) b - 1.0 else b).toDouble

    protected def makeBatch
    {
        sums   = sums ++ sum
        sumSqs = sumSqs ++ sumSq
        minXs  = minXs ++ minX
        maxXs  = maxXs ++ maxX
        means  = means ++ sum / (if (unbiased) batchSize - 1.0 else batchSize).toDouble
    } // makeBatch

    protected def clear
    {
        sum   = 0
        sumSq = 0
        minX  = Double.MaxValue
        maxX  = 0.0
    } // clear

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Tally the next value and update accumulators.
     *  @param x  the value to tally
     */
    override def tally (x: Double)
    {
        // reset stats for a new batch
        if (n % batchSize == 0) { makeBatch; clear }

        // tally
        n     += 1
        sum   += x
        sumSq += x * x
        if (x < minX) minX = x
        if (x > maxX) maxX = x
    } // tally

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Get the average minimum value among sample batches, ignoring the first 
     *  batch.
     */
    override def min: Double = if (den <= 0) 0.0 else minXs(1 to b).sum / den

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Get the maximum value in sample.
     */
    override def max: Double = if (den <= 0) 0.0 else maxXs(1 to b).sum / den
    
    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Compute/estimate the sample grand mean.
     */
    override def mean: Double = if (den <= 0) 0.0 else means(1 to b).sum / den

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Compute/estimate the sample variance.
     */
    override def variance: Double = if (den <= 0) 0.0 else sumSqs(1 to b).sum / den - mean * mean

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Generate a header of statistical labels as a string.
     */
    override def labels (): String =
    {
        "| %4s | %9s | %9s | %9s | %9s | %9s | %9s |".format ("num", "batches", "min", "max", "mean", "stdDev", "interval")
    } // labels

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Generate a row of statistical results as a string.
     */
    override def toString: String = 
    {
        "| %4d | %9d | %9.3f | %9.3f | %9.3f | %9.3f |".format (num, b, min, max, mean, stddev, interval ())
    } // toString

} // BatchStat class


